package wk.asm.test;

import jdk.internal.org.objectweb.asm.*;
import net.sf.cglib.proxy.Enhancer;
import wk.asm.classLoader.MyClassLoader;
import wk.asm.test.visitor.MyClassVisitor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by wangkuan at 2018-09-03 13:26
 */
public class ASMGettingStarted {
    private static MyClassLoader classLoader = new MyClassLoader();
    private static Class enhancedClass = null;
    private static final String className = Tester.class.getName();

    public static void main(String[] args) throws Exception {
//        testASM();

//        testEnhancedClass();
//        new Tester().run();

//        testEnhancedSubClass();

        enhancedByCGLIB();
    }

    private static void enhancedByCGLIB() throws ClassNotFoundException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Class.forName(className));
        enhancer.setCallback(new MyProxy());

        Tester o = (Tester)enhancer.create();
        o.run();
    }

    /**
     * 无法使用
     */
    private static Tester generateEnhancedTester() throws IllegalAccessException, InstantiationException, IOException {
        if (enhancedClass == null) {
            ClassReader cr = new ClassReader(className);
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            MySubClassVisitor subClassVisitor = new MySubClassVisitor(Opcodes.ASM5, cw);
            cr.accept(subClassVisitor, ClassReader.SKIP_DEBUG);
            byte[] data = cw.toByteArray();

            enhancedClass = classLoader.defineClassForName(className, data);
        }

        //异常：java.lang.IllegalAccessException: Class wk.asm.test.ASMGettingStarted can not access a member of class wk.asm.test.Tester$EnhancedByASM with modifiers ""
        return (Tester) enhancedClass.newInstance();
    }

    private static void testEnhancedSubClass() throws IOException {
        ClassReader cr = new ClassReader(className);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new MyClassVisitor(cw);

        cr.accept(cv, ClassReader.SKIP_DEBUG);
        byte[] data = cw.toByteArray();

        File file = new File("C:\\workspace\\bigfoot\\asm\\target\\classes\\wk\\asm\\test\\Tester$EnhancedByASM.class");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(data);
        fos.close();
    }

    private static void testEnhancedClass() throws IOException {
        ClassReader cr = new ClassReader(className);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new MyClassVisitor(cw);

        cr.accept(cv, ClassReader.SKIP_DEBUG);
        byte[] data = cw.toByteArray();

        File file = new File("C:\\workspace\\bigfoot\\asm\\target\\classes\\wk\\asm\\test\\Tester2.class");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(data);
        fos.close();
    }

    static ClassWriter createClassWriter(String className) {
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        //声明一个类，使用JDK1.8版本，public的类，父类是java.lang.Object，没有实现任何接口
        classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);

        //初始化一个无参的构造函数
        MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        //从局部变量表中取出第一个值，该值为对象的引用
        constructor.visitVarInsn(Opcodes.ALOAD, 0);
        //执行父类的init初始化
        constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        //从当前方法返回void
        constructor.visitInsn(Opcodes.RETURN);
        constructor.visitMaxs(1, 1);
        constructor.visitEnd();

        return classWriter;
    }

    static byte[] createVoidMethod(String className, String message) {
        //注意，这里需要把classname里面的.改成/，如com.asm.Test改成com/asm/Test
        ClassWriter classWriter = createClassWriter(className.replace('.', '/'));

        //创建run方法
        //()V表示函数，无参数，无返回值
        MethodVisitor runMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "run", "()V", null, null);
        //先获取一个java.io.PrintStream对象
        runMethod.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        //将int, float或String型常量值从常量池中推送至栈顶  (此处将message字符串从常量池中推送至栈顶[输出的内容])
        runMethod.visitLdcInsn(message);
        //执行println方法（执行的是参数为字符串，无返回值的println函数）
        runMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        runMethod.visitInsn(Opcodes.RETURN);
        runMethod.visitMaxs(1, 1);
        runMethod.visitEnd();

        return classWriter.toByteArray();
    }

    static byte[] createIntMethod(String className, int returnVal) throws Exception {
        //注意，这里需要把classname里面的.改成/，如com.asm.Test改成com/asm/Test
        ClassWriter classWriter = createClassWriter(className.replace('.', '/'));

        //创建get方法
        //()Ljava/lang/Integer;表示函数，无参数，返回值为：java.lang.Integer，注意最后面的分号，没有就会报错
        MethodVisitor method = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "getIntVal", "()I", null, null);
        //将单字节的常量值(-128~127)推送至栈顶(如果不是-128~127之间的数字，则不能用bipush指令)
        method.visitIntInsn(Opcodes.BIPUSH, returnVal);

        //从当前方法返回对象引用
        method.visitInsn(Opcodes.IRETURN);
        method.visitMaxs(1, 1);
        method.visitEnd();

        return classWriter.toByteArray();
    }

    static byte[] createIntegerMethod(String className, int returnVal) throws Exception {
        //注意，这里需要把classname里面的.改成/，如com.asm.Test改成com/asm/Test
        ClassWriter classWriter = createClassWriter(className.replace('.', '/'));

        //创建get方法
        //()Ljava/lang/Integer;表示函数，无参数，返回值为：java.lang.Integer，注意最后面的分号，没有就会报错
        MethodVisitor method = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "getIntVal", "()Ljava/lang/Integer;", null, null);
        //将单字节的常量值(-128~127)推送至栈顶(如果不是-128~127之间的数字，则不能用bipush指令)
        method.visitIntInsn(Opcodes.BIPUSH, returnVal);

        //返回Integer类型时
        String methodDescriptor = Type.getMethodDescriptor(Integer.class.getMethod("valueOf", int.class));
//        String methodDescriptor = "(I)Ljava/lang/Integer;";
        method.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Integer.class), "valueOf", methodDescriptor, false);

        //从当前方法返回对象引用
        method.visitInsn(Opcodes.ARETURN);
        method.visitMaxs(1, 1);
        method.visitEnd();

        return classWriter.toByteArray();
    }

    private static void testASM() throws Exception {
        String className = "wk.asm.test.Tester";

        byte[] classData = createVoidMethod(className, "This is my first ASM test");
        Class<?> clazz = new MyClassLoader().defineClassForName(className, classData);
        clazz.getMethods()[0].invoke(clazz.newInstance());

        byte[] classData1 = createIntMethod(className, 10);
        Class<?> clazz1 = new MyClassLoader().defineClassForName(className, classData1);
        Object num1 = clazz1.getMethods()[0].invoke(clazz1.newInstance());
        System.out.println(num1);

        byte[] classData2 = createIntegerMethod(className, 20);
        Class<?> clazz2 = new MyClassLoader().defineClassForName(className, classData2);
        Object num2 = clazz2.getMethods()[0].invoke(clazz2.newInstance());
        System.out.println(num2);
    }
}
